/**
*********************************************************************************************************
*               Copyright(c) 2015, Realtek Semiconductor Corporation. All rights reserved.
*********************************************************************************************************
* @file      miot_model_client.c
* @brief     xiaomi vendor model client
* @details
* @author    hector_huang
* @date      2019-04-15
* @version   v1.0
* *********************************************************************************************************
*/
#include <stdlib.h>
#include "miot_model.h"

typedef struct _mi_tid_list
{
    struct _mi_tid_list *pprev;
    struct _mi_tid_list *pnext;
} mi_tid_list_t;

typedef struct{
    uint8_t siid;
    uint8_t piid;
    uint8_t last_tid;
    mi_tid_list_t node;
} mi_vendor_tid_t;

static mi_tid_list_t mi_tid_vendor_list_head;

static uint8_t global_tid;
static bool global_tid_init = false;

uint8_t get_new_tid(void)
{
    if(false == global_tid_init){
        global_tid = rand();
        if ((NULL == mi_tid_vendor_list_head.pprev) &&
        (NULL == mi_tid_vendor_list_head.pnext))
        {
            mi_tid_vendor_list_head.pprev = &mi_tid_vendor_list_head;
            mi_tid_vendor_list_head.pnext = &mi_tid_vendor_list_head;
        }
        global_tid_init = true;
    }else{
        global_tid++;
    }
    return global_tid;
}

static void mi_tid_add_to_vendor(mi_vendor_tid_t *ppub)
{
    mi_tid_vendor_list_head.pprev->pnext = &ppub->node;
    ppub->node.pprev = mi_tid_vendor_list_head.pprev;
    mi_tid_vendor_list_head.pprev = &ppub->node;
    ppub->node.pnext = &mi_tid_vendor_list_head;
}

static mi_vendor_tid_t *mi_tid_vendor_get(mi_tid_list_t *phead, uint8_t siid, uint8_t piid)
{
    mi_tid_list_t *pnode = phead->pnext;
    mi_vendor_tid_t *ppub;
    for (; pnode != phead; pnode = pnode->pnext)
    {
        ppub = CONTAINER_OF(pnode, mi_vendor_tid_t, node);
        if (ppub->siid == siid && ppub->piid == piid)
        {
            return ppub;
        }
    }

    return NULL;
}

uint8_t get_vendor_tid(uint8_t siid, uint8_t piid)
{
    /* get new vendor tid */
    uint8_t res = get_new_tid();
    
    mi_vendor_tid_t *ppub = mi_tid_vendor_get(&mi_tid_vendor_list_head,siid,piid);
    if(NULL == ppub)
    {
        /* add to current vendor tid list */
        ppub = plt_malloc(sizeof(mi_vendor_tid_t), RAM_TYPE_DATA_ON);
        if (NULL != ppub)
        {
            ppub->siid = siid;
            ppub->piid = piid;
            ppub->last_tid = res;
            mi_tid_add_to_vendor(ppub);
        }
        else
        {
            printw("vendor model add tid fail: id 0x%02x%02x!", siid, piid);
            return res;
        }
    }
    else
    {
        /* same tid, get new */
        if(res == ppub->last_tid){
            res = get_new_tid();
        }
        /* save tid for every piid */
        ppub->last_tid = res;
    }
    return res;
}

uint8_t get_vendor_last_tid(uint8_t siid, uint8_t piid)
{
		/* get new vendor tid */
    uint8_t res = 0;
	
	if ((NULL == mi_tid_vendor_list_head.pprev) &&
    	(NULL == mi_tid_vendor_list_head.pnext))
	{
			mi_tid_vendor_list_head.pprev = &mi_tid_vendor_list_head;
			mi_tid_vendor_list_head.pnext = &mi_tid_vendor_list_head;
			
	}
	
    mi_vendor_tid_t *ppub = mi_tid_vendor_get(&mi_tid_vendor_list_head,siid,piid);
    if(NULL == ppub)
    {
        /* add to current vendor tid list */
        ppub = plt_malloc(sizeof(mi_vendor_tid_t), RAM_TYPE_DATA_ON);
        if (NULL != ppub)
        {
            ppub->siid = siid;
            ppub->piid = piid;
            ppub->last_tid = get_new_tid();
            mi_tid_add_to_vendor(ppub);
					
        }
        else
        {
            printw("get_vendor_last_tid fail: id 0x%02x%02x!", siid, piid);
            return res;
        }
    }
		res = ppub->last_tid ;
    return res;
}

void mi_vendor_tid_delete_all(void)
{
    if ((NULL == mi_tid_vendor_list_head.pprev) &&
        (NULL == mi_tid_vendor_list_head.pnext))
    {
        return;
    }
    /* remove all node from list */
    mi_tid_list_t *pnode = mi_tid_vendor_list_head.pnext;
    mi_vendor_tid_t *ppub = NULL;
    while (pnode != &mi_tid_vendor_list_head)
    {
        ppub = CONTAINER_OF(pnode, mi_vendor_tid_t, node);
        pnode = pnode->pnext;
        plt_free(ppub, RAM_TYPE_DATA_ON);
    }
    mi_tid_vendor_list_head.pprev = &mi_tid_vendor_list_head;
    mi_tid_vendor_list_head.pnext = &mi_tid_vendor_list_head;
}

static mesh_msg_send_cause_t miot_client_send(const mesh_model_info_p pmodel_info,
                                              uint16_t dst, uint16_t app_key_index,
                                              uint8_t *pmsg, uint16_t msg_len)
{
    mesh_msg_t mesh_msg;
    mesh_msg.pmodel_info = pmodel_info;
    access_cfg(&mesh_msg);
    mesh_msg.pbuffer = pmsg;
    mesh_msg.msg_len = msg_len;
    mesh_msg.dst = dst;
    mesh_msg.app_key_index = app_key_index;
    return access_send(&mesh_msg);
}

mesh_msg_send_cause_t miot_get(const mesh_model_info_p pmodel_info, uint16_t dst,
                               uint16_t app_key_index, uint8_t siid, uint8_t piid)
{
    miot_get_t msg;
    ACCESS_OPCODE_BYTE(msg.opcode, MESH_MSG_MIOT_GET);
    uint16_t msg_len = ACCESS_OPCODE_SIZE(MESH_MSG_MIOT_GET) + 2;
    msg.siid = siid;
    msg.piid = piid;

    return miot_client_send(pmodel_info, dst, app_key_index, (uint8_t *)&msg, msg_len);
}

mesh_msg_send_cause_t miot_set(const mesh_model_info_p pmodel_info, uint16_t dst,
                               uint16_t app_key_index, miot_payload_t *pdata, bool ack)
{
    miot_set_t msg;
    if (ack)
    {
        ACCESS_OPCODE_BYTE(msg.opcode, MESH_MSG_MIOT_SET);
    }
    else
    {
        ACCESS_OPCODE_BYTE(msg.opcode, MESH_MSG_MIOT_SET_UNACK);
    }

    uint16_t msg_len = ACCESS_OPCODE_SIZE(MESH_MSG_MIOT_SET) + MIOT_PARAM_MAX_LEN;
    msg.parameters.siid = pdata->siid;
    msg.parameters.piid = pdata->piid;
    msg.parameters.tid = get_new_tid();
    msg.parameters.type = (pdata->type) & 0x03;
    memcpy(msg.parameters.value, pdata->value, MIOT_VALUE_LEN);

    return miot_client_send(pmodel_info, dst, app_key_index, (uint8_t *)&msg, msg_len);
}

mesh_msg_send_cause_t miot_indication_ack(const mesh_model_info_p pmodel_info, uint16_t dst,
                                          uint16_t app_key_index, const uint8_t *pdata, uint8_t len)
{
    if (len > MIOT_PARAM_MAX_LEN)
    {
        return MESH_MSG_SEND_CAUSE_PAYLOAD_SIZE_EXCEED;
    }

    miot_indication_ack_t msg;
    ACCESS_OPCODE_BYTE(msg.opcode, MESH_MSG_MIOT_INDICATION_ACK);
    uint16_t msg_len = ACCESS_OPCODE_SIZE(MESH_MSG_MIOT_INDICATION_ACK) + len;
    memcpy(&msg.parameters, pdata, len);

    return miot_client_send(pmodel_info, dst, app_key_index, (uint8_t *)&msg, msg_len);
}

mesh_msg_send_cause_t miot_action(const mesh_model_info_p pmodel_info, uint16_t dst,
                                  uint16_t app_key_index, miot_action_payload_t *pdata, uint8_t len)
{
    if (len > MIOT_PARAM_MAX_LEN)
    {
        return MESH_MSG_SEND_CAUSE_PAYLOAD_SIZE_EXCEED;
    }

    miot_indication_t msg;
    ACCESS_OPCODE_BYTE(msg.opcode, MESH_MSG_MIOT_ACTION);
    uint16_t msg_len = ACCESS_OPCODE_SIZE(MESH_MSG_MIOT_ACTION) + len;
    memcpy(&msg.parameters, pdata, len);

    return miot_client_send(pmodel_info, dst, app_key_index, (uint8_t *)&msg, msg_len);
}

static bool miot_client_receive(mesh_msg_p pmesh_msg)
{
    bool ret = TRUE;
    uint8_t *pbuffer = pmesh_msg->pbuffer + pmesh_msg->msg_offset;
    mesh_model_info_p pmodel_info = pmesh_msg->pmodel_info;

    switch (pmesh_msg->access_opcode)
    {
    case MESH_MSG_MIOT_STATUS:
        {
            miot_client_status_t status_data;
            status_data.param_len = pmesh_msg->msg_len - ACCESS_OPCODE_SIZE(MESH_MSG_MIOT_STATUS);
            if (status_data.param_len > MIOT_PARAM_MAX_LEN)
            {
                status_data.param_len = MIOT_PARAM_MAX_LEN;
            }
            memcpy(status_data.parameters, pbuffer + MEMBER_OFFSET(miot_status_t, parameters),
                   status_data.param_len);
            if (NULL != pmodel_info->model_data_cb)
            {
                pmodel_info->model_data_cb(pmodel_info, MIOT_CLIENT_STATUS, &status_data);
            }
        }
        break;
    case MESH_MSG_MIOT_INDICATION:
        {
            miot_client_indication_t indication_data;
            indication_data.src = pmesh_msg->src;
            indication_data.app_key_index = pmesh_msg->app_key_index;
            indication_data.param_len = pmesh_msg->msg_len - ACCESS_OPCODE_SIZE(MESH_MSG_MIOT_INDICATION);
            if (indication_data.param_len > MIOT_PARAM_MAX_LEN)
            {
                indication_data.param_len = MIOT_PARAM_MAX_LEN;
            }
            memcpy(indication_data.parameters, pbuffer + MEMBER_OFFSET(miot_indication_t, parameters),
                   indication_data.param_len);
            if (NULL != pmodel_info->model_data_cb)
            {
                pmodel_info->model_data_cb(pmodel_info, MIOT_CLIENT_INDICATION, &indication_data);
            }
        }
        break;
    default:
        ret = FALSE;
        break;
    }
    return ret;
}

bool miot_client_reg(uint8_t element_index, mesh_model_info_p pmodel_info)
{
    if (NULL == pmodel_info)
    {
        return FALSE;
    }

    pmodel_info->model_id = MESH_MODEL_MIOT_CLIENT;
    if (NULL == pmodel_info->model_receive)
    {
        pmodel_info->model_receive = miot_client_receive;
        if (NULL == pmodel_info->model_data_cb)
        {
            printw("miot_client_reg: missing data process callback!");
        }
    }
    return mesh_model_reg(element_index, pmodel_info);
}

